/*
 *  Netzpult - Your desk on the web
 *  Copyright (C) 2011  Ulrich Hilger, http://uhilger.de
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/
 */

package de.uhilger.netzpult.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.PopupPanel;

/**
 * Abstrakte Basisklasse f�r Aufrufe an einen Server via GWT-RPC, die eine Java
 * EE konforme Authentifizierung erm�glicht. Implementierungen dieser Klasse
 * �berschreiben die Methode execute(result) oder auch execute() mit der
 * Implementierung des gew�nschten Serveraufrufs.
 * 
 * @author Copyright (c) Ulrich Hilger, http://uhilger.de
 * @author Published under the terms and conditions of the <a
 *         href="http://www.gnu.org/licenses/" target="_blank">GNU General
 *         Public License</a>
 */
@SuppressWarnings("rawtypes")
public abstract class ServerCall implements LinkedCommand, AsyncCallback, RequestCallback,
		LoginDialog.LoginDialogListener {

	/**
	 * Das Kommando das ausgef�hrt werden soll, wenn der Aufruf an den Server
	 * erfolgreich war
	 */
	private Command nextCommand;

	/**
	 * Das Kommando das ausgef�hrt werden soll, wenn der Aufruf an den Server eine
	 * Anmeldung erfordert, diese aber fehlschlug
	 */
	private Command exitCommand;

	/**
	 * Das Ergebnis des Kommandos dieses ServerCalls, es wird an nextCommand
	 * weiter gegeben, wenn dieser ServerCall erfolgreich ist
	 */
	private Object previousResult = null;

	/**
	 * Der Server-Dienst, der in execute() benutzt werden soll, kann null sein
	 */
	protected Object rpcService;

	/** login form element name */
	public static final String J_USERNAME = "j_username";
	/** login form element name */
	public static final String J_PASSWORD = "j_password";

	/**
	 * Ein Objekt der Klasse ServerCall erzeugen
	 * 
	 * @param service
	 *          hier kann ein Service geparkt werden, um ihn durch eine
	 *          Unterklasse von ServiceCall aufzurufen, kann null sein
	 * @param nextCommand
	 *          das Kommando, das ausgef�hrt werden soll, wenn diese Serveranfrage
	 *          erfolgreich war, kann null sein
	 * @param exitCommand
	 *          das Kommando, das ausgef�hrt werden soll, wenn diese Serveranfrage
	 *          abgebrochen wird, kann null sein
	 */
	public ServerCall(Object service, Command nextCommand, Command exitCommand) {
		// super();
		this.rpcService = service;
		this.nextCommand = nextCommand;
		this.exitCommand = exitCommand;
	}

	/**
	 * Ein Objekt der Klasse ServerCall erzeugen
	 * 
	 * @param nextCommand
	 *          das Kommando, das ausgef�hrt werden soll, wenn diese Serveranfrage
	 *          erfolgreich war, kann null sein
	 * @param exitCommand
	 *          das Kommando, das ausgef�hrt werden soll, wenn diese Serveranfrage
	 *          abgebrochen wird, kann null sein
	 */
	public ServerCall(Command nextCommand, Command exitCommand) {
		// this.serverCommand = serverCommand;
		this(null, nextCommand, exitCommand);
	}

	/**
	 * Dieses Kommando ausf�hren
	 */
	@Override
	public void execute() {
		execute(previousResult);
	}

	/**
	 * Mit dieser Methode kann dieser Instanz der Klasse ServerCall das
	 * Antwortobjekt eines zuvor abgelaufenen ServerCalls �bergeben werden
	 * 
	 * @param result
	 *          das Antwortobjekt des vorangegangenen ServerCalls
	 */
	public void setPreviousResult(Object result) {
		this.previousResult = result;
	}

	/**
	 * Diese Methode wird aufgerufen, wenn der Serveraufruf nicht erfolgreich war.
	 * Hier kann man pr�fen, ob der Fehler auf eine fehlende Autorisierung oder
	 * eine andere Ursache zur�ckgeht. Eine fehlende Autorisierung kann daran
	 * liegen, dass der Benutzer noch nicht am Server angemeldet ist, in diesem
	 * Fall k�me vom Server eine Anmeldeaufforderung, auf die hier gepr�ft wird.
	 * 
	 * @param caught
	 *          der Fehler, der hier zu verarbeiten ist
	 */
	@Override
	public void onFailure(Throwable caught) {
		testForSecurityCheck(caught, this, this);
	}

	/**
	 * Diese Methode wird aufgerufen, wenn der Serveraufruf erfolgreich war. In
	 * diesem Fall wird einfach das Kommando ausgef�hrt, das dieser Instanz der
	 * Klasse ServerCall f�r diesen Fall �bergeben wurde.
	 * 
	 * @param result
	 *          die Antwort des Servers
	 */
	@Override
	public void onSuccess(Object result) {
		if (nextCommand != null) {
			if (nextCommand instanceof LinkedCommand) {
				((LinkedCommand) nextCommand).setPreviousResult(result);
			}
			nextCommand.execute();
		}
	}

	/**
	 * Diese Methode wird aufgerufen, wenn der Server auf das Serverkommando
	 * dieser Instanz der Klasse ServerCall mit einer Anmeldeaufforderung
	 * geantwortet und daraufhin der Benutzer eine Anmeldung an den Server
	 * geschickt hat.
	 * 
	 * Hier wird die Antwort des Servers auf die Benutzeranmeldung verarbeitet,
	 * indem die Anfrage an den Server wiederholt wird, die zur
	 * Anmeldeaufforderung gef�hrt hatte.
	 * 
	 * @param request
	 *          die Serveranfrage, die zu dieser Antwort f�hrte
	 * @param response
	 *          die Antwort des Servers, die es zu verarbeiten gilt
	 */
	@Override
	public void onResponseReceived(Request request, Response response) {
		execute();
	}

	/**
	 * Diese Methode wird aufgerufen, wenn der Server auf das Serverkommando
	 * dieser Instanz der Klasse ServerCall mit einer Anmeldeaufforderung
	 * geantwortet und der Benutzer eine Anmeldung an den Server geschickt hat,
	 * die dieser nicht aktzeptierte. Hier wird die Antwort des Servers auf eine
	 * fehlgeschlagene Benutzeranmeldung verarbeitet. Die Fehlermeldung wird
	 * angezeigt und anschlie�end das Kommando zum Verlassen der Prozedur
	 * aufgerufen.
	 * 
	 * @param request
	 *          die Serveranfrage, die den Fehler erzeugt hat
	 * @param exception
	 *          der Fehler, den es zu verarbeiten gilt
	 */
	@Override
	public void onError(Request request, Throwable exception) {
		Window.alert(exception.getMessage());
		if (exitCommand != null) {
			exitCommand.execute();
		}
	}

	/**
	 * Eine Serverantwort auf eine Java EE konforme Anmeldeaufforderung
	 * �berpr�fen.
	 * 
	 * @param caught
	 *          der Fehler, der vom Server auf eine GWT-RPC-Anfrage hin zur�ck kam
	 * @param callback
	 *          der Callback, der die Antwort auf die Anmeldung verarbeitet
	 * @param listener
	 *          ein Listener, der darauf reagiert, wenn der Benutzer die Anmeldung
	 *          abbricht
	 */
	public void testForSecurityCheck(Throwable caught, RequestCallback callback, LoginDialog.LoginDialogListener listener) {
		// Window.alert(caught.getMessage());
		String msg = caught.getMessage();
		if (msg != null && msg.contains("j_security_check")) {
			// showLoginDialog(callback, listener);
			LoginDialog dlg;
			if (listener == null) {
				dlg = new LoginDialog("Bitte anmelden");
			} else {
				dlg = new LoginDialog("Bitte anmelden", listener);
			}
			dlg.addCloseHandler(new LoginDialogCloseHandler(callback));
			dlg.center();
			dlg.show();
			dlg.initFocus();
		}
	}

	/**
	 * Die Klasse LoginDialogCloseHandler verarbeitet das Ereignis des Schlie�ens
	 * des Login-Dialogs.
	 * 
	 * @author Copyright (c) Ulrich Hilger, http://uhilger.de
	 * @author Published under the terms and conditions of the <a
	 *         href="http://www.gnu.org/licenses/" target="_blank">GNU General
	 *         Public License</a>
	 */
	private class LoginDialogCloseHandler implements CloseHandler<PopupPanel> {
		/**
		 * Der Callback, der die Antwort des Servers auf die Anmeldung des Benutzers
		 * verarbeiten soll
		 */
		private RequestCallback callback;

		/**
		 * Ein neues Objekt der Klasse LoginDialogCloseHandler erzeugen
		 * 
		 * @param callback
		 *          der Callback, der die Antwort des Servers auf die Anmeldung des
		 *          Benutzers verarbeiten soll
		 */
		public LoginDialogCloseHandler(RequestCallback callback) {
			this.callback = callback;
		}

		/**
		 * Diese Methode wird ausgef�hrt, wenn der LoginDialog geschlossen wird und
		 * sendet die Anmeldung des Benutzers an den Server, wenn die OK-Taste
		 * bet�tigt wurde
		 */
		@Override
		public void onClose(CloseEvent<PopupPanel> event) {
			Object src = event.getSource();
			if (src instanceof LoginDialog) {
				LoginDialog dlg = (LoginDialog) src;
				if (dlg.didSave()) {
					sendLogin("", dlg.getUserName(), dlg.getUserPw(), callback);
				}
			}
		}
	}

	/**
	 * Die Anmeldung des Benutzers an den Server senden
	 * 
	 * @param senderName
	 *          der Name des Objekts, das die Anmeldung aufgerufen hat
	 * @param userName
	 *          der Benutzername
	 * @param pw
	 *          das Kennwort
	 * @param callback
	 *          der Callback, der die Antwort des Servers auf die Anmeldung des
	 *          Benutzers verarbeiten soll
	 */
	private void sendLogin(String senderName, String userName, String pw, RequestCallback callback) {
		HttpRequest request = new HttpRequest(GWT.getHostPageBaseURL() + "j_security_check", callback);
		request.addHeader("Content-Type", "application/x-www-form-urlencoded");
		request.setType(HttpRequest.REQUEST_TYPE_POST);
		request.addParameter(J_USERNAME, userName);
		request.addParameter(J_PASSWORD, pw);
		try {
			request.send();
		} catch (RequestException ex) {
			Window.alert("Error in " + senderName + ", message: " + ex.getMessage());
		}
	}

	/**
	 * Diese Methode wird aufgerufen, wenn der Login-Dialog vom Benutzer
	 * abgebrochen wurde Sie ruft das an ServerCall �bergebene exitCommand auf
	 */
	@Override
	public void loginCancelled() {
		if (exitCommand != null) {
			exitCommand.execute();
		}
	}

	/**
	 * Diese Methode ist hier implementiert, ohne etwas zu tun. Wenn eine
	 * Implementierung dieser Klasse die Methode execute(Object result) aus der
	 * Schnittstelle LinkedCommand ben�tigt, kann sie sie einfach �berschreiben,
	 * ansonsten kann sie sie weglassen.
	 */
	public void execute(Object result) {
		/*
		 * tue nichts
		 */
	}

}
